/*********************************************************
**    Konami PCM controller                              *
**********************************************************
Changelog, Hiromitsu Shioya 02/05/2002
fix start address decode timing. (sample loop bug.)

Changelog, Mish, August 1999:
Removed interface support for different memory regions per channel.
Removed interface support for differing channel volume.

Added bankswitching.
Added support for multiple chips.

(Nb:  Should different memory regions per channel be needed
the bankswitching function can set this up).

NS990821
support for the k007232_VOL() macro.
added external port callback, and functions to set the volume of the channels


Port to SMSE2K courtesy of unknownfile
*/

#include "smse2kincludes.h"
#include "k007232.h"
#include "sounddevicefactory.h"
#include <math.h>


/*
typedef struct kdacApcm
{
	UINT8			vol[KDAC_A_PCM_MAX][2];	// volume for the left and right channel
	UINT32			addr[KDAC_A_PCM_MAX];
	UINT32			start[KDAC_A_PCM_MAX];
	UINT32			step[KDAC_A_PCM_MAX];
	UINT32			bank[KDAC_A_PCM_MAX];
	int				play[KDAC_A_PCM_MAX];

	UINT8 			wreg[0x10];	// write data
	UINT8 *			pcmbuf[2];	// Channel A & B pointers

	UINT32  		clock;          // chip clock
	UINT32  		pcmlimit;

	sound_stream * 	stream;
	const k007232_interface *intf;
	UINT32 			fncode[0x200];
} KDAC_A_PCM;
*/

#define   BASE_SHIFT    (12)


/************************************************/
/*    Konami PCM update                         */
/************************************************/

int CK007232::Buffer(short** out, int len) {
  int i;
  int samples = len;

  short* outputs[2] = { out[0], out[1] };


  memset(outputs[0],0,samples * sizeof(*outputs[0]));
  memset(outputs[1],0,samples * sizeof(*outputs[1]));

  

  for( i = 0; i < KDAC_A_PCM_MAX; i++ )
    {
      if (this->play[i])
	{
	  int volA,volB,j,out;
	  unsigned int addr, old_addr;
	  //int cen;

	  /**** PCM setup ****/
	  addr = this->start[i] + ((this->addr[i]>>BASE_SHIFT)&0x000fffff);
	  volA = this->vol[i][0] * 2;
	  volB = this->vol[i][1] * 2;

	  for( j = 0; j < samples; j++ )
	    {
	      old_addr = addr;
	      addr = this->start[i] + ((this->addr[i]>>BASE_SHIFT)&0x000fffff);
	      while (old_addr <= addr)
		{
		  if( (this->pcmbuf[i][old_addr] & 0x80) || old_addr >= this->pcmlimit )
		    {
		      /* end of sample */

		      if( this->wreg[0x0d]&(1<<i) )
			{
			  /* loop to the beginning */
			  this->start[i] =
			    ((((unsigned int)this->wreg[i*0x06 + 0x04]<<16)&0x00010000) |
			     (((unsigned int)this->wreg[i*0x06 + 0x03]<< 8)&0x0000ff00) |
			     (((unsigned int)this->wreg[i*0x06 + 0x02]    )&0x000000ff) |
			     this->bank[i]);
			  addr = this->start[i];
			  this->addr[i] = 0;
			  old_addr = addr; /* skip loop */
			}
		      else
			{
			  /* stop sample */
			  this->play[i] = 0;
			}
		      break;
		    }

		  old_addr++;
		}

	      if (this->play[i] == 0)
		break;

	      this->addr[i] += this->step[i];

	      out = (this->pcmbuf[i][addr] & 0x7f) - 0x40;

	      outputs[0][j] += out * volA;
	      outputs[1][j] += out * volB;
	    }
	}
	 
    }


  return len;
}


/************************************************/
/*    Konami PCM start                          */
/************************************************/
void CK007232::Init(CMixer* mix, unsigned int clock) {
//	static const k007232_interface defintrf = { 0 };
	int i;
//	KDAC_A_PCM *info = get_safe_token(device);

	/* Set up the chips */

	this->pcmbuf[0] = NULL; //device->region;
	this->pcmbuf[1] = NULL; //device->region;
	this->pcmlimit  = NULL; //device->regionbytes;

	this->locked = 1;
	this->clock = clock;

	for( i = 0; i < KDAC_A_PCM_MAX; i++ )
	{
		this->start[i] = 0;
		this->step[i] = 0;
		this->play[i] = 0;
		this->bank[i] = 0;
	}
	this->vol[0][0] = 255;	/* channel A output to output A */
	this->vol[0][1] = 0;
	this->vol[1][0] = 0;
	this->vol[1][1] = 255;	/* channel B output to output B */

	for( i = 0; i < 0x10; i++ )  this->wreg[i] = 0;

	//info->stream = stream_create(device,0,2,device->clock/128,info,KDAC_A_update);
 
	//mix->CreateNewBuffer(this, clock / 128);

	 for( i = 0; i < 0x200; i++ )
		    this->fncode[i] = (32 << BASE_SHIFT) / (0x200 - i);
	 
	 Msg("K007232 inited, but we need a sample ROM before playing stuff!\n");
}

void CK007232::SetRom(int slot, CMemory* rom) {

	
	this->pcmbuf[0] = rom->GetRawMemory();
	this->pcmbuf[1] = rom->GetRawMemory(); //device->region;
	this->pcmlimit  = rom->GetSize(); //device->regionbytes;


}

/************************************************/
/*    Konami PCM write register                 */
/************************************************/
void CK007232::Write(UINT64 offset, UINT8 data) {
  int r = offset;
  int v = data;

  // Fake registers to handle bankswitching
  if (r == 0xfffe) { // Channel 1
		this->bank[0] = data<<17;
		return;
  }

  if (r == 0xffff) { // Channel 2
	  this->bank[1] = data << 17;
	  return;
  }

  this->wreg[r] = v;			/* stock write data */

  if (r == 0x0c){
    /* external port, usually volume control */
    // TODO: Make this work (stuff like AJAX uses it)
	 //if (info->intf->portwritehandler) (*info->intf->portwritehandler)(device,v);
    return;
  }
  else if( r == 0x0d ){
    /* loopflag. */
    return;
  }
  else{
    int  reg_port;

    reg_port = 0;
    if (r >= 0x06){
      reg_port = 1;
      r -= 0x06;
    }

    switch (r){
    case 0x00:
    case 0x01:
	{
				/**** address step ****/
      int idx = (((((unsigned int)this->wreg[reg_port*0x06 + 0x01])<<8)&0x0100) | (((unsigned int)this->wreg[reg_port*0x06 + 0x00])&0x00ff));
#if 0
      if( !reg_port && r == 1 )
	logerror("%04x\n" ,idx );
#endif

      this->step[reg_port] = this->fncode[idx];
      break;
	}
    case 0x02:
    case 0x03:
    case 0x04:
      break;
    case 0x05:
				/**** start address ****/
      this->start[reg_port] =
	((((unsigned int)this->wreg[reg_port*0x06 + 0x04]<<16)&0x00010000) |
	 (((unsigned int)this->wreg[reg_port*0x06 + 0x03]<< 8)&0x0000ff00) |
	 (((unsigned int)this->wreg[reg_port*0x06 + 0x02]    )&0x000000ff) |
	 this->bank[reg_port]);
      if (this->start[reg_port] < this->pcmlimit ){
	this->play[reg_port] = 1;
	this->addr[reg_port] = 0;
      }
      break;
    }
  }
}

/************************************************/
/*    Konami PCM read register                  */
/************************************************/
UINT8 CK007232::Read(UINT64 offset)
{
 int r = offset;
  int  ch = 0;



  if( r == 0x0005 || r == 0x000b ){
    ch = r/0x0006;
    r  = ch * 0x0006;

    this->start[ch] =
      ((((unsigned int)this->wreg[r + 0x04]<<16)&0x00010000) |
       (((unsigned int)this->wreg[r + 0x03]<< 8)&0x0000ff00) |
       (((unsigned int)this->wreg[r + 0x02]    )&0x000000ff) |
       this->bank[ch]);

    if (this->start[ch] <  this->pcmlimit ){
      this->play[ch] = 1;
      this->addr[ch] = 0;
    }
  }
  return 0;
}

void CK007232::Reset() {

}


void CK007232::Shutdown() {

}



SOUND_FACTORY("K007232",1,CK007232);